/**
 * 游戏类，负责管理游戏的主要逻辑，如难度设置、任务状态处理等。
 */
class Game {
    /**
     * 游戏类的构造函数，初始化游戏世界、难度设置，并启动游戏循环。
     */
    constructor() {
        this.world = new World();

        this.age = 0;
        this.currentLevelIndex = 0;

        const savedDifficulty = parseInt(localStorage['df']);
        this.difficulty = savedDifficulty >= 0
            ? DIFFICULTY_SETTINGS[savedDifficulty]
            : inputMode == INPUT_MODE_TOUCH
            ? DIFFICULTY_EASY
            : DIFFICULTY_NORMAL;

        if (SCREENSHOT) {
            CANVAS_WIDTH = 4096;
            CANVAS_HEIGHT = 4096;
            can.width = CANVAS_WIDTH;
            can.height = CANVAS_HEIGHT;

            const world = new World();

            const camera = firstItem(world.bucket('camera'));
            camera.zoom = 20;

            const chopper = new Chopper();
            chopper.angle = PI / 6;
            chopper.age = -0.01;
            world.add(chopper);

            world.render();

            throw new Error('plz taek screenshot');
        }

        (async () => {
            while (true) {
                await this.gameLoop();
            }
        })();
    }

    /**
     * 循环切换游戏难度，并更新本地存储和显示提示信息。
     */
    cycleDifficulty() {
        let index = DIFFICULTY_SETTINGS.indexOf(this.difficulty);
        index = (index - 1 + DIFFICULTY_SETTINGS.length) % DIFFICULTY_SETTINGS.length;
        this.difficulty = DIFFICULTY_SETTINGS[index];
        localStorage['df'] = index;

        for (const exposition of this.world.bucket('exposition')) {
            exposition.world.remove(exposition);
        }

        if (!this.pauseWorld) {
            (async () => {
                const exposition = new Exposition([
                    nomangle('难度: ') + this.difficulty[0],
                ]);
                this.world.add(exposition);

                await exposition.complete();
                await exposition.agesBy(2);
                exposition.world.remove(exposition);
            })();
        }
    }

    /**
     * 创建一个难度提示框，允许玩家更改游戏难度。
     * @param {boolean} withText - 是否显示提示文本。
     * @returns {Object} - 难度提示框对象。
     */
    difficultyPrompt(withText) {
        const text = () => withText
            ? nomangle('难度: ') + this.difficulty[0] + nomangle(' - 按 [K] 键更改')
            : '';

        const prompt = new StartPrompt(
            text(),
            [75],
            () => {
                this.cycleDifficulty();
                if (this.world) {
                    applyDifficulty(this.world, this.difficulty);
                }

                prompt.text = text();
            },
        );
        return prompt;
    }

    /**
     * 处理任务失败的逻辑，显示失败界面并进行过渡动画。
     */
    async missionFailed() {
        this.pauseWorld = new World();

        const worldOut = new TransitionOut();
        this.pauseWorld.add(worldOut);
        await worldOut.agesBy(0.3);

        this.paused = true;
        this.world.destroy();

        const title = new Title(nomangle('任务\n失败'), '#f00');
        this.pauseWorld.add(title);
        title.fade(0, 1, 0.2);
        await title.agesBy(1);

        const transitionOut = new TransitionOut();
        this.pauseWorld.add(transitionOut);
        await transitionOut.agesBy(0.3);

        this.paused = false;
        this.pauseWorld = null;
    }

    /**
     * 处理任务成功的逻辑，显示成功界面并进行过渡动画。
     */
    async missionSuccess() {
        this.pauseWorld = new World();

        const worldOut = new TransitionOut();
        this.pauseWorld.add(worldOut);
        await worldOut.agesBy(0.3);

        this.paused = true;
        this.world.destroy();

        const title = new Title(nomangle('任务\n成功'), '#fff');
        this.pauseWorld.add(title);
        title.fade(0, 1, 0.2);
        await title.agesBy(1);

        const transitionOut = new TransitionOut();
        this.pauseWorld.add(transitionOut);
        await transitionOut.agesBy(0.3);

        this.paused = false;
        this.pauseWorld = null;
    }

    /**
     * 创建一个关卡选择提示框。
     * @param {boolean} withText - 是否显示提示文本。
     * @returns {StartPrompt} - 关卡选择提示框对象。
     */
    levelSelectPrompt(withText) {
        const text = () => withText
            ? nomangle('关卡: ') + (this.selectedLevel + 1) + nomangle(' - 按 [L] 键切换')
            : '';

        const prompt = new StartPrompt(
            text(),
            [76],
            () => {
                this.selectedLevel = (this.selectedLevel + 1) % this.levels.length;
                prompt.text = text();
            },
        );
        return prompt;
    }

    /**
     * 显示游戏标题界面，包含开始提示、难度、静音和关卡选择设置选项。
     */
    async titleScreen() {
        this.paused = true;
        this.pauseWorld = new World();

        const title = new Title(nomangle('飞鹰直升机'), '#fff', '#000');

        const promptSet = new PromptSet([
            new StartPrompt(
                inputMode === INPUT_MODE_TOUCH
                    ? nomangle('点击部署')
                    : nomangle('按 [空格键] 部署'),
                [32],
                () => {
                    promptSet.world.remove(promptSet);
                },
            ),
            inputMode != INPUT_MODE_TOUCH
                ? this.difficultyPrompt(true)
                : null,
            inputMode != INPUT_MODE_TOUCH
                ? this.mutePrompt(true)
                : null,
            inputMode != INPUT_MODE_TOUCH
                ? this.levelSelectPrompt(true)
                : null,
        ]);

        this.pauseWorld.add(
            title,
            promptSet,
            new TransitionIn(),
        );

        await promptSet.removed();
        this.paused = false;
        await title.fade(1, 0, 1, 0.3);

        this.pauseWorld = null;
    }

    /**
     * 创建一个静音提示框，允许玩家切换音乐的开关状态。
     * @param {boolean} withText - 是否显示提示文本。
     * @returns {StartPrompt} - 静音提示框对象。
     */
    mutePrompt(withText) {
        const text = () => withText
            ? nomangle('音乐: ') + (SONG_VOLUME > 0 ? nomangle('开启') : nomangle('关闭')) + nomangle(' - 按 [M] 键') + (SONG_VOLUME > 0 ? nomangle('静音') : nomangle('开启'))
            : '';

        const prompt = new StartPrompt(
            text(),
            [77],
            () => {
                setSongVolume(SONG_VOLUME > 0 ? 0 : 0.5);
                prompt.text = text();
            },
        );
        return prompt;
    }

    /**
     * 暂停游戏，销毁当前游戏世界并显示暂停界面。
     * 暂停界面包含继续、难度选择、静音切换和关卡选择功能。
     */
    pause() {
        if (this.pauseWorld) {
            return;
        }

        this.world.destroy();

        this.paused = true;
        this.pauseWorld = new World();

        const title = new Title(nomangle('已暂停'), 'rgba(0,0,0,0)', 'rgba(0,0,0,0.95)');

        let selectedLevelChanged = false;
        const promptSet = new PromptSet([
            new StartPrompt(nomangle('按 [空格键] 继续'), [32, 27, 80], () => {
                promptSet.world.remove(promptSet);
            }),
            this.difficultyPrompt(true),
            this.mutePrompt(true),
            this.levelSelectPrompt(true),
        ]);
        this.pauseWorld.add(title, promptSet);

        (async () => {
            await promptSet.removed();
            this.pauseWorld = null;
            this.paused = false;

            // 如果选择了新的关卡，重新开始游戏循环
            if (this.selectedLevel !== this.currentLevelIndex) {
                this.world.destroy();
                this.currentLevelIndex = this.selectedLevel;
                this.gameLoop();
            }

            DOWN = {};
        })();
    }

    /**
     * 游戏主循环，负责管理关卡流程、游戏状态更新和界面显示。
     * 游戏会依次加载并执行 levels 数组中定义的各个关卡。
     * 在每个关卡开始前，会根据 attemptIndex 判断是否为首次尝试，并显示标题界面或过渡动画。
     * 关卡执行过程中会处理用户输入、物理碰撞、敌人行为等逻辑。
     * 关卡结束后，会根据结果显示成功或失败界面，并可能触发难度调整提示。
     * 当所有关卡完成后，会显示游戏结束总结界面。
     */
    async gameLoop() {
        // 定义游戏关卡列表
        this.levels = [
            tutorialFly,
            firstMountain,
            mountainThenCeiling,
            tutorialShoot,
            caveThenCeiling,
            lowCeiling,
            mountainChopperCeilingChopper,
            hardMountains,
            tightSqueezes,
            doubleChopperNonsense,
            smallMountainSuccession,
            nightMountains,
            upAndDown,
        ];

        // 初始化关卡和尝试索引
        this.selectedLevel = this.currentLevelIndex || 0;
        let levelIndex = this.selectedLevel;
        let attemptIndex = 0;

        // 记录游戏开始时间和当前任务开始时间
        let startTime = this.age;
        let missionStartTime = this.age;

        // 初始化统计数据
        let totalDeaths = 0;
        let lowestDifficultyIndex = 9;
        let promptedEasyMode = false;
        let missionFailures = 0;

        // 计算总囚犯数量
        const totalPrisoners = this.levels.reduce((sum, level) => {
            const world = new World();
            level(world);
            return sum + Array.from(world.bucket('prisoner')).length;
        }, 0);
        let totalRescuedPrisoners = 0;
        

        // 首次尝试显示标题界面
        if (attemptIndex++ === 0) {
          await this.titleScreen();
          levelIndex = this.selectedLevel; // 从选择的关卡开始
          startTime = this.age;

          playSong();
        }
        
        // 主游戏循环，遍历所有关卡
        while (levelIndex < this.levels.length) {
            // 更新当前关卡索引
            this.currentLevelIndex = levelIndex;
            
            // 销毁旧世界并创建新世界
            if (this.world) this.world.destroy();
            this.world = new World();

            // 添加难度和静音提示
            this.world.add(this.difficultyPrompt(false));
            this.world.add(this.mutePrompt(false));

            // 初始化当前任务的最低难度索引
            let lowestDifficultyIndexInMission = 9;
            const level = this.levels[levelIndex];

            try {
                // 设置关卡环境
                const settings = [
                    sunset,
                    daytime,
                    night,
                ];
                const setting = settings[~~(levelIndex / 2) % settings.length];
                this.world.add(...setting());

                // 执行关卡逻辑
                const levelPromise = level(this.world);

                // 应用当前难度设置
                applyDifficulty(this.world, this.difficulty);

                // 强制更新摄像机
                const camera = firstItem(this.world.bucket('camera'));
                camera.cycle(2);

                // 获取当前任务的囚犯数量
                const missionPrisoners = Array.from(this.world.bucket('prisoner')).length;

                // 后续尝试添加过渡动画
                this.world.add(new TransitionIn());

                // 如果任务失败次数为0，显示任务开始信息
                if (missionFailures === 0) {
                    missionStartTime = this.age;

                    if (levelIndex > 0) {
                        (async () => {
                            await camera.agesBy(1);

                            // 显示任务背景信息
                            const exposition = new Exposition([
                                COUNTRIES[levelIndex % COUNTRIES.length],
                                MONTHS[levelIndex % MONTHS.length] + ' ' + (1 + (levelIndex * 7) % 27) + ', ' + (2017 + ~~(levelIndex / 2)),
                            ]);
                            this.world.add(exposition);

                            await exposition.complete();
                            await exposition.agesBy(2);
                            exposition.world.remove(exposition);
                        })();
                    }
                }

                // 添加暂停游戏的提示
                this.world.add(new StartPrompt('', [27, 80], () => {
                    this.pause();
                }));

                // 添加任务进度指示器
                this.world.add(new ProgressIndicator(() => {
                    const player = firstItem(this.world.bucket('player'));
                    return [
                        [nomangle('任务'), `${levelIndex + 1}/${this.levels.length}`],
                        [nomangle('囚犯'), (player ? player.rescuedPrisoners : 0) + '/' + missionPrisoners],
                        [nomangle('时间'), formatTime(this.age - missionStartTime)],
                        [nomangle('总时间'), formatTime(this.age - startTime)],
                        [nomangle('难度 [K]'), this.difficulty[0]],
                    ];
                }));

                // 更新当前任务的最低难度索引
                this.world.waitFor(() => {
                    lowestDifficultyIndexInMission = min(lowestDifficultyIndexInMission, DIFFICULTY_SETTINGS.indexOf(this.difficulty));
                });

                // 如果是触摸模式，添加移动控制
                if (inputMode === INPUT_MODE_TOUCH) {
                    this.world.add(new MobileControls());
                }

                // 等待关卡完成
                await levelPromise;

                // 重置任务失败次数
                missionFailures = 0;
                lowestDifficultyIndex = min(lowestDifficultyIndex, lowestDifficultyIndexInMission);

                // 更新总解救囚犯数量
                const player = firstItem(this.world.bucket('player'));
                if (player) totalRescuedPrisoners += player.rescuedPrisoners;

                // 根据关卡索引显示不同界面
                if (levelIndex === 0) {
                    await this.exposition();
                } else {
                    await this.missionSuccess();
                }

                // 进入下一关卡
                levelIndex++;

            } catch (err) {
                // 处理任务失败
                totalDeaths++;
                missionFailures++;
                console.log(err);

                await new Promise(resolve => setTimeout(resolve, 250));
                await this.missionFailed();

                // 如果任务失败次数达到5次且难度为正常模式，提示切换到简单模式
                if (missionFailures % 5 === 0 && this.difficulty === DIFFICULTY_NORMAL && !promptedEasyMode) {
                    promptedEasyMode = true;
                    if (confirm(nomangle('启用简单模式？（简化物理效果，敌人攻击性降低）'))) {
                        this.difficulty = DIFFICULTY_EASY;
                    }
                }
            }
        }

        // 销毁游戏世界
        this.world.destroy();

        // 显示游戏结束总结
        await this.runRecap([
            [nomangle('总时间'), formatTime(this.age - startTime)],
            [nomangle('难度'), DIFFICULTY_SETTINGS[lowestDifficultyIndex][0]],
            [nomangle('解救囚犯'), `${totalRescuedPrisoners}/${totalPrisoners}`],
            [nomangle('坠毁次数'), `${totalDeaths}`],
            ['', ''],
        ], [
            nomangle(`我用时 `),
            formatTime(this.age - startTime),
            nomangle(' 完成了 直升机，坠毁了 '),
            totalDeaths,
            nomangle(' 次，解救了 '),
            totalRescuedPrisoners,
            '/',
            totalPrisoners,
            ' 名囚犯！(',
            DIFFICULTY_SETTINGS[lowestDifficultyIndex][0],
            ' 模式)',
        ].join(''));
    }

    /**
     * 显示游戏开场介绍动画。
     */
    async exposition() {
        this.pauseWorld = new World();

        const worldOut = new TransitionOut();
        this.pauseWorld.add(worldOut);
        await worldOut.agesBy(0.3);
        this.paused = true;
        this.world.destroy();

        {
            this.pauseWorld = new World();
            const exposition = new Exposition([
                nomangle('当所有希望都失去时，世界警察组织派出了 飞鹰直升机。'),
                ' '.repeat(10),
                nomangle('他们负责执行最危险的任务。'),
            ], true);
            this.pauseWorld.add(
                new Background('#000', '#000'),
                exposition,
            );

            await exposition.complete();
            await exposition.agesBy(1);
        }

        {
            this.pauseWorld = new World();
            const exposition = new Exposition([
                nomangle('就连最邪恶的恐怖分子也害怕 飞鹰直升机。'),
                ' '.repeat(10),
                nomangle('这就是他们的故事。'),
            ], true);
            this.pauseWorld.add(
                new Background('#000', '#000'),
                exposition,
            );

            await exposition.complete();
            await exposition.agesBy(1);
        }

        this.paused = false;
        this.pauseWorld = null;
    }

    /**
     * 显示游戏结束后的总结回顾界面。
     * @param {Array<Array<string>>} recap - 包含回顾信息的数组，每个子数组代表一行回顾，格式为 [标签, 值]。
     * @param {string} tweetText - 用于分享到社交媒体的文本内容。
     */
    async runRecap(recap, tweetText) {
        this.pauseWorld = new World();
        this.paused = true;

        const transitionIn = new Transition(1);
        this.pauseWorld.add(transitionIn);
        await transitionIn.agesBy(0.3);

        const title = new Title(nomangle('感谢游玩'), '#fff');

        const promptSet = new PromptSet([
            ...recap.map(([label, value]) => new RunRecap(label, value)),
            new StartPrompt(
                inputMode === INPUT_MODE_TOUCH
                    ? nomangle('点击重新部署')
                    : nomangle('按 [空格键] 重新部署'),
                [32],
                () => promptSet.world.remove(promptSet),
            ),
            inputMode != INPUT_MODE_TOUCH
                ? new StartPrompt(
                    nomangle('按 [T] 键分享你的分数'),
                    [84],
                    () => tweet(tweetText),
                )
                : null
        ])

        this.pauseWorld.add(title, promptSet);

        await promptSet.removed();

        this.paused = false;
        this.pauseWorld = null;
    }

    /**
     * 游戏每一帧的更新逻辑。
     * @param {number} elapsed - 自上一帧以来经过的时间（秒）。
     */
    cycle(elapsed) {
        const before = performance.now();
        this.age += elapsed;

        if (!this.pauseWorld || !this.paused) {
            this.world.cycle(min(elapsed, 1 / 30));
        }

        this.world.render();

        if (this.pauseWorld) {
            this.pauseWorld.cycle(elapsed);
            this.pauseWorld.render();
        }

        const after = performance.now();

        if (DEBUG) {
            ctx.fillStyle = '#fff';
            ctx.strokeStyle = '#000';
            ctx.textAlign = nomangle('left');
            ctx.textBaseline = nomangle('bottom');
            ctx.font = nomangle('14pt Courier');
            ctx.lineWidth = 3;

            const player = firstItem(this.world.bucket('player'));

            let y = CANVAS_HEIGHT - 10;
            for (const line of [
                nomangle('帧率: ') + ~~(1 / elapsed),
                nomangle('理论帧率: ') + ~~(1000 / (after - before)),
                nomangle('实体数量: ') + this.world.entities.size,
                nomangle('玩家位置: ') + (player ? `${~~player.x},${~~player.y}` : ''),
            ].reverse()) {
                ctx.strokeText(line, 10, y);
                ctx.fillText(line, 10, y);
                y -= 20;
            }
        }
    }
}
